namespace Wilco.CodeHilighter.Engine.SyntaxHighlighting
{
    using System;
    using System.Drawing;
    using System.Linq;
    using System.Xml.Linq;

    /// <summary>
    /// Represents a comment block scanner.
    /// </summary>
    public class CommentBlockScanner : ScannerBase
    {
        #region Fields

        private CommentBlockNode commentBlockNode;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.CommentBlockScanner"/> class.
        /// </summary>
        public CommentBlockScanner()
            : this(null, null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.CommentBlockScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        public CommentBlockScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
            : this(tokenizer, scannerResult, new CommentBlockNode())
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.CommentBlockScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="commentBlockNode">A <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.CommentBlockNode"/> object.</param>
        public CommentBlockScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, CommentBlockNode commentBlockNode)
            : base(tokenizer, scannerResult)
        {
            this.commentBlockNode = commentBlockNode;
            this.SetID("CommentBlockScanner");
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the comment block node.
        /// </summary>
        public CommentBlockNode CommentBlockNode
        {
            get
            {
                return this.commentBlockNode;
            }
            set
            {
                if (value != this.commentBlockNode)
                {
                    this.commentBlockNode = value;
                }
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new CommentBlockScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XElement element = (XElement)state;

            // Load settings.
            this.commentBlockNode.BackColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting","name", "BackColor"));
            this.commentBlockNode.ForeColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "ForeColor"));
            this.commentBlockNode.Font = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "Font");
            this.commentBlockNode.NavigateUrl = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "NavigateUrl");

            // Load entities.
            foreach (XElement entityElement in element.Descendants("entities").Descendants("entity"))
            {
                this.commentBlockNode.Entities.Add(
                    new Entity(
                        entityElement.Attribute("start").Value,
                        entityElement.Attribute("end").Value));
            }
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            //XDocument document = (XDocument)container;
            XElement element = (XElement)base.SaveState(container);

            // Save settings.
            XElement settingRootElement = new XElement("settings");
            element.Add(settingRootElement);

            settingRootElement.Add(this.CreateSetting("BackColor", ColorTranslator.ToHtml(this.commentBlockNode.BackColor)));
            settingRootElement.Add(this.CreateSetting("ForeColor", ColorTranslator.ToHtml(this.commentBlockNode.ForeColor)));
            settingRootElement.Add(this.CreateSetting("Font", this.commentBlockNode.Font));
            settingRootElement.Add(this.CreateSetting("NavigateUrl", this.commentBlockNode.NavigateUrl));

            // Save entities.
            XElement entityRootElement = new XElement("entities");
            element.Add(entityRootElement);
            XElement entityElement;
            foreach (Entity entity in this.commentBlockNode.Entities)
            {
                entityElement = new XElement("entity");
                entityElement.SetAttributeValue("start", entity.Start);
                entityElement.SetAttributeValue("end", entity.End);
                entityRootElement.Add(entityElement);
            }

            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation will generally have a reference to a 
        /// <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!this.Enabled)
            {
                if (this.Child != null)
                {
                    this.Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                for (int i = 0; i < this.commentBlockNode.Entities.Count; i++)
                {
                    if ((this.Tokenizer.Position + this.commentBlockNode.Entities[i].Start.Length) <= this.Tokenizer.Source.Length)
                    {
                        if (this.Tokenizer.GetNextTokens(this.commentBlockNode.Entities[i].Start.Length) == this.commentBlockNode.Entities[i].Start)
                        {
                            isMatch = true;

                            int endOfLineIndex = this.Tokenizer.Source.IndexOf(this.commentBlockNode.Entities[i].End, this.Tokenizer.Position);
                            if (endOfLineIndex == -1)
                                endOfLineIndex = this.Tokenizer.Source.Length;
                            else
                                endOfLineIndex += this.commentBlockNode.Entities[i].End.Length;

                            this.ScannerResult.Add(new Occurrence(this.Tokenizer.Position, endOfLineIndex - this.Tokenizer.Position, this.commentBlockNode));
                            this.Tokenizer.MoveTo(endOfLineIndex - 1);
                            break;
                        }
                    }
                }

                if (!isMatch)
                {
                    if (this.Child != null)
                    {
                        this.Child.Scan(token);
                    }
                }
            }
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private XElement CreateSetting(string name, string value)
        {
            return new XElement("setting", value, new XAttribute("name", name));
        }

        #endregion Methods
    }
}